#ifndef __TILETAG__
#define __TILETAG__

#include <array>
#include <tuple>
#include <iostream>

#include "TupleHelper.hpp"
#include "constants.h"

using namespace std;

template<int TILETAGDIM, int TASKDIM>
class TileTag {
public:

  std::array<int64_t, TILETAGDIM> m_arr;

  TileTag() = delete;


  // template<int64_t...Sizes, int ...S>
  // TileTag(seq<S...>) : TileTag(std::get<S>(params) ...) {}

  template <typename... Sizes> constexpr 
  TileTag(Sizes... sizes) : m_arr{{std::forward<Sizes>(sizes)...}}{
    static_assert(TILETAGDIM == sizeof...(Sizes), "ERROR");
  }

  constexpr TileTag(std::array<int64_t, TILETAGDIM> arr) : m_arr(arr) {}

  // template <typename... Sizes> constexpr 
  // TileTag(std::tuple<Sizes...> sizes) : m_arr{{fill_array_from_tuple(sizes)}}
  //   static_assert(TILETAGDIM == sizeof...(Sizes), "ERROR");
  // }

  // // PERFORMANCE BUG !
  // // This is likely low perf because it is not a static conversion, need to
  // // revisit this but variadic templates are tricky !
  // template <typename... Sizes> 
  // TileTag(std::tuple<Sizes...> sizes) {
  //   static_assert(TILETAGDIM == sizeof...(Sizes), "ERROR");
  //   tupleAsArray<int64_t, Sizes...>::make(m_arr, sizes);
  // }

  constexpr auto toTuple() const -> 
    decltype(arrayAsTuple<int64_t, TILETAGDIM>::make(m_arr)) 
  {
    return arrayAsTuple<int64_t, TILETAGDIM>::make(m_arr);
  }

  constexpr TileTag(const TileTag &tt) : m_arr(tt.m_arr) {}  

  constexpr bool operator==(const TileTag &tt) const {
    return (tt.m_arr == m_arr);
  }

  constexpr operator size_t() const {
    return TILETAGDIM * sizeof (int64_t);
  }

  // Find a workaround to make this constexpr
  // See for instance: https://gist.github.com/1457531
  int64_t operator[](int64_t i) const {
    return m_arr[i];
  }
};


// template <int TILETAGDIM, int TASKDIM> 
// size_t cnc_tag_hash_compare<TileTag<TILETAGDIM, TASKDIM> >::hash
// (const TileTag<TILETAGDIM, TASKDIM>& tt) const;

// template <int TILETAGDIM, int TASKDIM>
// constexpr bool cnc_tag_hash_compare< TileTag<TILETAGDIM, TASKDIM> >::equal
// (const TileTag<TILETAGDIM, TASKDIM>& a, const TileTag<TILETAGDIM, TASKDIM>& b) const;

// template <int TILETAGDIM, int TASKDIM>
// std::ostream & cnc_format
// ( std::ostream& os, const TileTag<TILETAGDIM, TASKDIM> & t );


ostream& operator<<(ostream& stream, TileTag<TILETAGDIMMACRO, INTERTASKDIMMACRO> const &tt);

#endif // __TILETAG__
